package com.github.byakkili.bim.core;

import cn.hutool.core.collection.CollUtil;
import com.github.byakkili.bim.core.handler.connect.ConnectHandler;
import com.github.byakkili.bim.core.listener.ListenerHandler;
import com.github.byakkili.bim.core.protocol.Protocol;
import com.github.byakkili.bim.core.support.netty.NettyEventLoopFactory;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.util.Optional;
import java.util.stream.Collectors;

/**
 * @author Guannian Li
 */
@Slf4j
public class BimNettyServer {
    /**
     * B-IM上下文
     */
    @Getter
    private final BimContext context;
    /**
     * Netty server bootstrap
     */
    private ServerBootstrap bootstrap;

    private Channel channel;
    private EventLoopGroup bossGroup;
    private EventLoopGroup workerGroup;

    public BimNettyServer(BimConfiguration config) {
        this.context = new BimContext(config);

        this.bootstrap = new ServerBootstrap();
        this.bossGroup = NettyEventLoopFactory.eventLoopGroup(1, "BimServerBoss");
        this.workerGroup = NettyEventLoopFactory.eventLoopGroup(config.getIoThread(), "BimServerWorker");
    }

    /**
     * 启动
     */
    public synchronized void start() {
        BimConfiguration config = context.getConfig();

        log.info("B-IM initialized with {}:{}", config.getBindIp(), config.getBindPort());

        log.info("Using protocols [{}]", CollUtil.join(config.getProtocols().stream().map(Protocol::name).collect(Collectors.toList()), ", ", "\"", "\""));
        config.getSessionListeners().forEach(listener -> log.info("Session listeners: {}", listener));
        config.getCommandInterceptors().forEach(interceptor -> log.info("Command interceptor: {}", interceptor));
        config.getCommandProcessors().forEach((command, processor) -> log.info("Command processor: {} -> {}", command, processor));

        final ConnectHandler connectHandler = new ConnectHandler(context);
        final LoggingHandler debugLogHandler = new LoggingHandler(LogLevel.DEBUG);
        final ListenerHandler listenerHandler = new ListenerHandler(context);

        bootstrap.group(bossGroup, workerGroup)
                .handler(debugLogHandler)
                .channel(NettyEventLoopFactory.serverSocketChannelClass())
                .option(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
                .childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE)
                .childOption(ChannelOption.SO_KEEPALIVE, config.isKeepAlive())
                .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
                .childHandler(new ChannelInitializer<Channel>() {
                    @Override
                    protected void initChannel(Channel channel) {
                        channel.pipeline()
                                .addLast("listener-handler", listenerHandler)
                                .addLast("connect-handler", connectHandler);
                    }
                });

        // bind.
        ChannelFuture channelFuture = bootstrap.bind(config.getBindIp(), config.getBindPort());
        channelFuture.syncUninterruptibly();
        channel = channelFuture.channel();

        log.info("B-IM started on {}:{}", config.getBindIp(), config.getBindPort());
    }

    /**
     * 关闭
     */
    public synchronized void stop() {
        try {
            if (channel != null) {
                // unbind.
                channel.close();
            }
        } catch (Exception e) {
            log.warn(e.getMessage(), e);
        }
        try {
            context.getTopics().getStrSetMap().values().forEach(sessions ->
                    Optional.ofNullable(sessions).ifPresent(s -> s.forEach(BimSession::close))
            );
        } catch (Exception e) {
            log.warn(e.getMessage(), e);
        }
        try {
            bossGroup.shutdownGracefully().syncUninterruptibly();
            workerGroup.shutdownGracefully().syncUninterruptibly();
        } catch (Exception e) {
            log.warn(e.getMessage(), e);
        }
    }
}
